package org.bouncycastle.crypto.agreement.kdf;

import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.DerivationFunction;
import org.bouncycastle.crypto.DerivationParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.OutputLengthException;
import org.bouncycastle.crypto.params.KDFParameters;

/**
 * Generator for Concatenation Key Derivation Function defined in NIST SP 800-56A, Sect 5.8.1
 */
public class ConcatenationKDFGenerator
    implements DerivationFunction
{
    private Digest  digest;
    private byte[]  shared;
    private byte[]  otherInfo;
    private int     hLen;

    /**
     * @param digest the digest to be used as the source of generated bytes
     */
    public ConcatenationKDFGenerator(
        Digest digest)
    {
        this.digest = digest;
        this.hLen = digest.getDigestSize();
    }

    public void init(
        DerivationParameters    param)
    {
        if (param instanceof KDFParameters)
        {
            KDFParameters p = (KDFParameters)param;

            shared = p.getSharedSecret();
            otherInfo = p.getIV();
        }
        else
        {
            throw new IllegalArgumentException("KDF parameters required for generator");
        }
    }

    /**
     * return the underlying digest.
     */
    public Digest getDigest()
    {
        return digest;
    }

    /**
     * int to octet string.
     */
    private void ItoOSP(
        int     i,
        byte[]  sp)
    {
        sp[0] = (byte)(i >>> 24);
        sp[1] = (byte)(i >>> 16);
        sp[2] = (byte)(i >>> 8);
        sp[3] = (byte)(i >>> 0);
    }

    /**
     * fill len bytes of the output buffer with bytes generated from
     * the derivation function.
     *
     * @throws DataLengthException if the out buffer is too small.
     */
    public int generateBytes(
        byte[]  out,
        int     outOff,
        int     len)
        throws DataLengthException, IllegalArgumentException
    {
        if ((out.length - len) < outOff)
        {
            throw new OutputLengthException("output buffer too small");
        }

        byte[]  hashBuf = new byte[hLen];
        byte[]  C = new byte[4];
        int     counter = 1;
        int     outputLen = 0;

        digest.reset();

        if (len > hLen)
        {
            do
            {
                ItoOSP(counter, C);

                digest.update(C, 0, C.length);
                digest.update(shared, 0, shared.length);
                digest.update(otherInfo, 0, otherInfo.length);

                digest.doFinal(hashBuf, 0);

                System.arraycopy(hashBuf, 0, out, outOff + outputLen, hLen);
                outputLen += hLen;
            }
            while ((counter++) < (len / hLen));
        }

        if (outputLen < len)
        {
            ItoOSP(counter, C);

            digest.update(C, 0, C.length);
            digest.update(shared, 0, shared.length);
            digest.update(otherInfo, 0, otherInfo.length);

            digest.doFinal(hashBuf, 0);

            System.arraycopy(hashBuf, 0, out, outOff + outputLen, len - outputLen);
        }

        return len;
    }
}
